package br.com.ac.fatureweb.integration.business;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import br.com.ac.fatureweb.integration.billing.ValorVO;
import br.com.ac.fatureweb.integration.model.EconomiaVO;
import br.com.ac.fatureweb.integration.model.FaixaVO;
import br.com.ac.fatureweb.integration.model.ImovelVO;
import br.com.ac.fatureweb.integration.model.TarifaSocialVO;
import br.com.ac.fatureweb.integration.model.TarifaVO;
import br.com.ac.fatureweb.integration.utilitary.NumberUtils;

/**
 * Calcular o valor a ser pago por um consumo informado.
 * 
 * Exemplo de cálculo: <br/>
 * 
 * Então teríamos um imóvel com 3 economias totalizando 4 economias:
 * 
 * 1 – residencial; 2 – comercial; 1 – pública.
 * 
 * Calculando o consumo de 50m³: 50(consumo) / 4(economias) = 12,5: serão
 * calculados 12m³ em cada tarifa e somado seu valor ao termino:
 * 
 * 1 – residencial = 10 * 1,00 = 10,00 + 2 * 1,31 = 2,62 #12,62.
 * 
 * 2 – comercial = 10 * 1,99 = 19,90 + 2 * 2,34 = 4,68 #24,58 24,58 * 2
 * (quantidade de economias que usam esta tarifa) #49,16.
 * 
 * 1 – pública = 20 * 2,58 = 51,60 Obs.: o mínimo desta tarifa  é 20, está
 * informado.
 * 
 * Total da água = 12,62 + = 49,16 + 51,60 = #113,38
 * 
 * Total de esgoto  = 50% no valor da água = 113,38 * 0.5 = 56,69
 * 
 * Serviço extra = 20% no valor da água = 113,38 113,38 * 0.2 = 22,676 = 22,68.
 * 
 * Total faturado  = 113,38 + 56,69 + 22,68  = 192,75.
 * 
 * Obs: consumo quando dividido é “TRUNCADO” exemplo 43/4 = 10,75 = 10
 * 
 * @autor Aldivone Correia - 10/01/2015
 *
 */
public class CalcularTarifa {

	private ImovelVO imovel;

	private Integer codProduto;

	public CalcularTarifa(ImovelVO imovel, Integer codProduto) {
		super();
		this.imovel = imovel;
		this.codProduto = codProduto;
	}

	/**
	 * Calcular os valores em faixa de acordo com o consumo informado para
	 * tarifas e economias informadas.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * 
	 * @param consumoMes
	 * @param codTarifa
	 * @param lstTarifa
	 * @param lstFaixa
	 * @param tarifaSocial
	 * @param lstEconomia
	 * 
	 * @return
	 */
	public List<ValorVO> calcular(Long consumoMes, Long codTarifa,
			List<TarifaVO> lstTarifa, List<FaixaVO> lstFaixa,
			TarifaSocialVO tarifaSocial, List<EconomiaVO> lstEconomia) {

		// Tratamento para o consumidor de tarifa social.
		if (isConsumidorSocial(tarifaSocial)) {
			consumoMes = getConsumoMinimoTarifaSocial(consumoMes, tarifaSocial);
		}

		List<ValorVO> lstValorACobrar = null;

		// Se o imóvel tiver apenas uma categoria.
		if (lstEconomia == null) {

			lstValorACobrar = calcularEconomiaPrincipal(consumoMes, codTarifa,
					lstTarifa, lstFaixa, 1);
		}

		// Se o imóvel tiver mais de uma categoria - Misto.
		if (lstEconomia != null && !lstEconomia.isEmpty()) {

			Long consumoPorEconomia = consumoMes / getQtdeEconomia(lstEconomia);

			// Calcular o valor da economia principal.
			lstValorACobrar = calcularEconomiaPrincipal(consumoPorEconomia,
					codTarifa, lstTarifa, lstFaixa, 1);

			for (EconomiaVO economia : lstEconomia) {

				TarifaVO tarifa = findTarifa(economia.getCodTarifa(), lstTarifa);

				// Localizar as faixas da tarifa.
				List<FaixaVO> lstFaixaTarifa = findListaFaixa(
						economia.getCodTarifa(), lstFaixa);

				lstValorACobrar.addAll(calcularPorEconomia(consumoPorEconomia,
						economia.getCodTarifa(), tarifa, lstFaixaTarifa,
						economia.getQtdEconomias()));
			}
		}

		return lstValorACobrar;
	}

	private Long getQtdeEconomia(List<EconomiaVO> lstEconomia) {
		Long qtdeEconomia = 1L;

		for (EconomiaVO economia : lstEconomia) {
			qtdeEconomia += economia.getQtdEconomias();
		}

		return qtdeEconomia;
	}

	private List<ValorVO> calcularEconomiaPrincipal(Long consumoMes,
			Long codTarifa, List<TarifaVO> lstTarifa,
			List<FaixaVO> lstFaixaPorTarifa, Integer qtdEconomia) {
		List<ValorVO> lstValorACobrar;
		TarifaVO tarifa = findTarifa(codTarifa, lstTarifa);

		// Localizar as faixas da tarifa.
		List<FaixaVO> lstFaixaTarifa = findListaFaixa(codTarifa,
				lstFaixaPorTarifa);

		lstValorACobrar = calcularPorEconomia(consumoMes, codTarifa, tarifa,
				lstFaixaTarifa, qtdEconomia);
		return lstValorACobrar;
	}

	private static List<FaixaVO> findListaFaixa(Long codTarifa,
			List<FaixaVO> lstFaixaPorTarifa) {
		List<FaixaVO> lstFaixaTarifa = getLstFaixaTarifa(codTarifa,
				lstFaixaPorTarifa);

		if (lstFaixaTarifa == null) {
			throw new IllegalArgumentException(
					"Não foram encontrada faixa para tarifa informada, "
							+ "não será possível calcular a conta.");
		}
		return lstFaixaTarifa;
	}

	private TarifaVO findTarifa(Long codTarifa, List<TarifaVO> lstTarifa) {
		TarifaVO tarifa = getTarifa(codTarifa, lstTarifa);

		if (tarifa == null) {
			throw new IllegalArgumentException(
					"Para calcular o valor de uma conta deverá existir uma tarifa.");
		}
		return tarifa;
	}

	private List<ValorVO> calcularPorEconomia(Long consumoMes, Long codTarifa,
			TarifaVO tarifa, List<FaixaVO> lstFaixaTarifa, Integer qtdEconomia) {

		List<ValorVO> lstValorACobrar = new ArrayList<ValorVO>();

		Long consumoSaldo = consumoMes;

		for (FaixaVO faixa : lstFaixaTarifa) {

			/*
			 * Quando o flag de cobrança máxima for SIM. Localizar a faixa onde
			 * o consumo de encaixa e cobrar pelo máximo da faixa vezes o preço
			 * dela.
			 */
			if (faixa.isCobrancaMaxima()
					&& consumoMes.intValue() >= faixa.getInicio().intValue()
					&& consumoMes.intValue() <= faixa.getFim().intValue()) {

				ValorVO valorVO = calcularConsumoPorCobrancaMaxima(codTarifa,
						consumoMes, tarifa, faixa, qtdEconomia);

				// Quando essa condição acontecer só deverá ter uma faixa de
				// valor calculada. Cálculo igual da Coelce.
				lstValorACobrar = new ArrayList<ValorVO>();

				lstValorACobrar.add(valorVO);

				break;

			} else {

				Long consumoFaixaT = calcularConsumoNaFaixa(faixa);

				// Consumo maior que a faixa.
				if (consumoSaldo > consumoFaixaT) {
					ValorVO valorVO = calcularNaFaixa(codTarifa, consumoFaixaT,
							tarifa, faixa, qtdEconomia);

					lstValorACobrar.add(valorVO);

					consumoSaldo -= valorVO.getConsumo();
				}

				// Se o consumo for menor que a faixa.
				else {
					ValorVO valorVO = calcularNaFaixa(codTarifa, consumoSaldo,
							tarifa, faixa, qtdEconomia);

					lstValorACobrar.add(valorVO);

					break;
				}
			}
		}

		return lstValorACobrar;
	}

	private Long calcularConsumoNaFaixa(FaixaVO faixa) {
		Long consumoM3 = 0L;

		if (faixa.getInicio().equals(0L)) {
			consumoM3 = faixa.getFim() - faixa.getInicio();
		} else {
			consumoM3 = (faixa.getFim() - faixa.getInicio()) + 1;
		}
		return consumoM3;
	}

	private ValorVO calcularConsumoPorCobrancaMaxima(Long codTarifa,
			Long consumoMes, TarifaVO tarifa, FaixaVO faixa, Integer qtdEconomia) {

		ValorVO valorVO = calcularNaFaixa(codTarifa, faixa.getFim(), tarifa,
				faixa, qtdEconomia);

		return valorVO;
	}

	private ValorVO calcularNaFaixa(Long codTarifa, Long consumo,
			TarifaVO tarifa, FaixaVO faixa, Integer qtdEconomia) {
		ValorVO valorVO = new ValorVO();

		Double valorNaFaixa = calcular(consumo, faixa.getValor());

		valorVO.setInicio(faixa.getInicio());
		valorVO.setFim(faixa.getFim());
		valorVO.setIdTarifa(codTarifa);

		// Calcular por economia.
		valorVO.setValorUnitario(faixa.getValor());
		valorVO.setQtdEconomia(qtdEconomia);

		valorVO.setValor(valorNaFaixa * qtdEconomia);

		valorVO.setConsumo(consumo);
		valorVO.setCategoria(tarifa.getSigla());

		valorVO.setValorAnterior(0D);

		valorVO.setIdCarga(imovel.getIdCarga());
		valorVO.setInscricao(imovel.getInscricao());
		valorVO.setCodProduto(codProduto);

		return valorVO;
	}

	/**
	 * Calcular o valor dos metros informados.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param metros
	 * @param valor
	 * @return
	 */
	private Double calcular(Long metros, Double valor) {
		Double valorCalculado = metros * valor;

		return NumberUtils.truncateValue(valorCalculado);
	}

	/**
	 * Localizar as faixa de tarifa de acordo com o código da tarifa.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param codTarifa
	 * @param lstFaixa
	 * @return
	 */
	private static List<FaixaVO> getLstFaixaTarifa(Long codTarifa,
			List<FaixaVO> lstFaixa) {

		if (lstFaixa != null && !lstFaixa.isEmpty()) {

			List<FaixaVO> lstRetorno = new ArrayList<FaixaVO>();

			for (FaixaVO faixaVO : lstFaixa) {

				if (faixaVO.getCodTarifa().equals(codTarifa)) {

					lstRetorno.add(faixaVO);
				}
			}

			// Ordernar as faixa pelos campos de inicio e fim.
			Collections.sort(lstRetorno, new FaixaComparator());

			return lstRetorno;
		}

		return null;
	}

	/**
	 * Localizar os dados da tarifa a ser calculada.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param codTarifa
	 * @param lstTarifa
	 * @return
	 */
	private static TarifaVO getTarifa(Long codTarifa, List<TarifaVO> lstTarifa) {

		if (lstTarifa != null && !lstTarifa.isEmpty()) {

			for (TarifaVO tarifa : lstTarifa) {

				if (tarifa.getCodigo().equals(codTarifa)) {

					return tarifa;
				}
			}
		}

		return null;
	}

	/**
	 * Determinar se o consumidor e social ou não.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param tarifaSocial
	 * @return
	 */
	public static boolean isConsumidorSocial(TarifaSocialVO tarifaSocial) {
		return tarifaSocial != null ? true : false;
	}

	/**
	 * Verificar se o consumo do mês é menor que o consumo mínino
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @param consumoMes
	 * @param tarifaSocial
	 * @return
	 */
	private Long getConsumoMinimoTarifaSocial(Long consumoMes,
			TarifaSocialVO tarifaSocial) {

		if (tarifaSocial.getConsumoMinimo() != null
				&& tarifaSocial.getConsumoMinimo().intValue() < consumoMes
						.intValue()) {
			return tarifaSocial.getConsumoMinimo();
		}

		return consumoMes;

	}

	/**
	 * Localizar o consumo mínimo da tarifa.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 * @return
	 */
	public static Long getMinimoTarifa(Long codTarifa, List<FaixaVO> lstFaixa) {

		// Localizar as faixas da tarifa.
		List<FaixaVO> lstFaixaTarifa = findListaFaixa(codTarifa, lstFaixa);

		if (lstFaixaTarifa != null && !lstFaixaTarifa.isEmpty()) {

			Collections.sort(lstFaixaTarifa, new FaixaComparator());

			return lstFaixaTarifa.iterator().next().getFim();
		}

		throw new IllegalArgumentException(
				"Não existe faixa para a tarifa de código: " + codTarifa);
	}

	/**
	 * Ordenar as faixa pelo campos de inicio e fim.
	 * 
	 * @autor Aldivone Correia - 10/01/2015
	 *
	 */
	private static class FaixaComparator implements Comparator<FaixaVO> {

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		@Override
		public int compare(FaixaVO faixa1, FaixaVO faixa2) {

			int compareTo = faixa1.getInicio().compareTo(faixa2.getInicio());

			if (compareTo == 0) {
				compareTo = faixa1.getFim().compareTo(faixa2.getFim());
			}

			return compareTo;
		}
	}

	public static void main(String[] args) {

		Integer qtdEconomia = 2;
		qtdEconomia += 1;

		System.out.println(qtdEconomia);
	}

}
